package core.proto.annotation;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;

import org.jdesktop.swingx.JXMapViewer;
import org.jdesktop.swingx.mapviewer.GeoPosition;

import core.proto.AllComponents;
import core.proto.MapWaypointRenderer;
import core.proto.Util;

public class MapAnnotationUI extends DocAnnotationUI implements AnnotationRemovalListener, AnnotationFocusListener {

	private JXMapViewer map;
	private WaypointIcon waypointIcon;

	private List<WaypointRemovalListener> removalListeners;

	public MapAnnotationUI(AnnotationModel model) throws CannotAddAnnotationException  {
		super(model);
		this.map = AllComponents.getMap();
		Point2D p = map.convertGeoPositionToPoint(((MapAnnotationModel) model).getWaypointIconPos());
		waypointIcon = new WaypointIcon((int) p.getX(), (int) p.getY());
		removalListeners = new LinkedList<WaypointRemovalListener>();
	}

	public void recomputeVisibility (GeoPosition upperLeft, GeoPosition lowerRight, JXMapViewer map) {
		MapAnnotationModel m = (MapAnnotationModel) model;
		// Check if waypoint still visible (within screen boundary of map).
		// If so, make visible if currently invisible and update screen position. 
		// If no, make it invisible.
		if (withinBounds(upperLeft, lowerRight, m.getWaypointIconPos())){
			Point2D pt = map.convertGeoPositionToPoint(m.getWaypointIconPos());


			waypointIcon.waypointScreenX = (int) pt.getX();
			waypointIcon.waypointScreenY = (int) pt.getY();
			waypointIcon.setVisible(true);
		} else {
			waypointIcon.setVisible(false);
		}
		// Check if annotation still visible (within screen boundary of map).
		// If so, make visible if currently invisible and update screen position. 
		// If no, make it invisible.
		// First check if it is not being edited. 
		if (! isEditing()){
			// Change its geo position. 
			if (withinBounds(upperLeft, lowerRight, m.getWaypointTextPos())) {
				Point2D pt = map.convertGeoPositionToPoint(m.getWaypointTextPos());
				Point textScreenLoc = new Point((int) pt.getX(), (int) pt.getY());
				label.setLocation(textScreenLoc);
				shrink(textScreenLoc.getX(), textScreenLoc.getY());
			} else {
				hide();
			}
		}
	}
	
	protected void doFocusLostModelViewUpdates () {
		// Update the geo position of the text. 
		MapAnnotationModel model = (MapAnnotationModel) this.model;

		// old semantics where the annotation gets repainted to the same spot the popup frame is when it disappears. 
//		GeoPosition pos = this.map.convertPointToGeoPosition(this.frame.getLocation());
//		model.setWaypointTextPos(pos);
//		shrink(frame.getX(), frame.getY());
		
		// new semantics where annotation ALWAYS sticks to the same spot as the waypoint.
		model.setWaypointTextPos(model.getWaypointIconPos()); // TODO no longer necessary to keep 2 separate positions.
		Point2D screenLoc = map.convertGeoPositionToPoint(model.getWaypointIconPos());
		shrink(screenLoc.getX(), screenLoc.getY()); 
	}
	
	public void addToPane(JLayeredPane pane){
		super.addToPane(pane);
		pane.add(waypointIcon, JLayeredPane.MODAL_LAYER);
		pane.repaint();
	}
	
	public void removeFromPane() {
		parent.remove(waypointIcon);
		super.removeFromPane();
	}

	private boolean withinBounds(GeoPosition upperLeft, GeoPosition lowerRight, GeoPosition pos) {
		return  Util.lte(pos.getLatitude(), Math.max(upperLeft.getLatitude(), lowerRight.getLatitude())) &&
				Util.lte(Math.min(upperLeft.getLatitude(), lowerRight.getLatitude()), pos.getLatitude()) &&
				Util.lte(pos.getLongitude(), Math.max(upperLeft.getLongitude(), lowerRight.getLongitude())) &&
				Util.lte(Math.min(upperLeft.getLongitude(), lowerRight.getLongitude()), pos.getLongitude());
	}

	public boolean clickFallsIn (Point clickLoc) {
		if (this.waypointIcon.isVisible()) {
			ImageIcon img = waypointIcon.waypointToDisplay;
			Rectangle r = new Rectangle(waypointIcon.waypointScreenX - img.getIconWidth() / 2, 
					waypointIcon.waypointScreenY- img.getIconHeight(), 
					img.getIconWidth(), img.getIconHeight());
			return r.contains(clickLoc);
		}
		return false;
	}

	private class WaypointIcon extends JLabel {

		private static final long serialVersionUID = 1L;
		public ImageIcon waypointToDisplay; 
		public int waypointScreenX;
		public int waypointScreenY;

		public WaypointIcon(int x, int y){
			this.waypointScreenX = x;
			this.waypointScreenY = y;
			waypointToDisplay = WaypointImageManager.getMatchingColorWaypoint();
			// Label's visible rectangle will be as large as possible, starting at (0,0) (the same screen position as the upper
			// left corner of its container). So, when we render, we can directly use the coordinates of the waypoint with 
			// respect to the waypoint's container.
			this.setSize((int)Toolkit.getDefaultToolkit().getScreenSize().getWidth(), (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight());
		}

		protected void paintComponent(Graphics g){
			MapWaypointRenderer.paintWaypointAt(g, waypointScreenX, waypointScreenY, waypointToDisplay);
		}
	}



	@Override
	public void lostFocus() {
		// We need to update the location of annotations that have been just edited.
		Point p = getEditorLocation();
		// Transform to map coordinate. 
		((MapAnnotationModel) model).setWaypointTextPos(map.convertPointToGeoPosition(p));
		this.label.setLocation(p);
	}



	@Override
	public void gainedFocus() {

	}

	public void addWaypointRemovalListener(WaypointRemovalListener l) {
		removalListeners.add(l);
	}

	@Override
	public void annotationRemoved(Object source) {
		// The annotation has already removed itself from the container. 
		// We just need to remove the icon here. 
		parent.remove(label);
		// Traverse the list in reverse order
		// in case we add removals in future so that 
		// removals won't affect our traversal. 
		for(int i = removalListeners.size()-1; i>=0; --i){
			removalListeners.get(i).waypointRemoved(this);
		}
		parent.repaint();
	}
}
